package com.zyl.blog.common.utils.Extreme;

import com.zyl.blog.common.enums.MessageEnum;
import lombok.Getter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Component;

import java.time.Duration;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 国际化消息工具类
 * 支持：枚举/Code获取、参数格式化、指定Locale、缓存优化、默认值兜底
 * @version v1.0
 * @ProjectName: Personal_Blog_Server
 * @Package: com.zyl.blog.common.utils.Extreme
 * @ClassName: MessageUtils
 * @Author FinalFantasy
 * @Date 2025/9/12-09:03
 */
@Component
public class MessageUtils {

    // ==================== 核心依赖与缓存配置 ====================
    /**
     * 静态持有MessageSource（Spring注入后赋值，避免业务代码手动注入）
     */
    private static MessageSource messageSource;

    /**
     * 消息本地缓存（线程安全）：key=消息Code+Locale，value=消息内容
     */
    private static final Map<String, CachedMessage> MESSAGE_CACHE = new ConcurrentHashMap<>();

    /**
     * 缓存默认过期时间（可根据业务调整，生产环境建议30分钟~2小时）
     */
    private static final Duration CACHE_EXPIRE_DURATION = Duration.ofMinutes(30);


    // ==================== Spring注入（静态变量赋值） ====================
    /**
     * 注入MessageSource（Spring启动时自动调用，确保初始化顺序）
     * 注：不能直接用@Autowired标注静态变量，需通过非静态setter注入
     */
    @Autowired
    public void setMessageSource(@NonNull MessageSource messageSource) {
        MessageUtils.messageSource = messageSource;
        LogUtil.info("MessageUtils 初始化成功，MessageSource已注入");
    }


    // ==================== 核心方法：按枚举获取消息（推荐） ====================
    /**
     * 按枚举获取消息（使用当前请求的Locale，无参数）
     * @param messageEnum 消息枚举（不可为null）
     * @return 国际化消息（若不存在则返回枚举Code）
     */
    public static String getMessage(@NonNull MessageEnum messageEnum) {
        return getMessage(messageEnum, null, null, LocaleContextHolder.getLocale());
    }

    /**
     * 按枚举获取消息（使用当前请求的Locale，带参数）
     * @param messageEnum 消息枚举（不可为null）
     * @param args 消息参数（如 "{0}不能为空" 中的参数，可为null）
     * @return 国际化消息（若不存在则返回枚举Code）
     */
    public static String getMessage(@NonNull MessageEnum messageEnum, @Nullable Object... args) {
        return getMessage(messageEnum, args, null, LocaleContextHolder.getLocale());
    }

    /**
     * 按枚举获取消息（指定Locale，带参数，默认值兜底）
     * @param messageEnum 消息枚举（不可为null）
     * @param args 消息参数（可为null）
     * @param defaultValue 消息不存在时的默认值（可为null，null时返回枚举Code）
     * @param locale 指定的Locale（不可为null，建议用LocaleContextHolder.getLocale()或Locale.CHINA）
     * @return 国际化消息（优先缓存→消息源→默认值→枚举Code）
     */
    public static String getMessage(
            @NonNull MessageEnum messageEnum,
            @Nullable Object[] args,
            @Nullable String defaultValue,
            @NonNull Locale locale
    ) {
        // 校验枚举非空
        Objects.requireNonNull(messageEnum, "消息枚举MessageEnum不可为null");
        // 调用通用方法（枚举Code作为消息Code）
        return getMessageByCode(messageEnum.getCode(), args, defaultValue, locale);
    }


    // ==================== 基础方法：按Code获取消息（灵活场景） ====================
    /**
     * 按消息Code获取消息（使用当前请求的Locale，无参数）
     * @param code 消息Code（如 "common.success"，不可为null/空）
     * @return 国际化消息（若不存在则返回Code）
     */
    public static String getMessageByCode(@NonNull String code) {
        return getMessageByCode(code, null, null, LocaleContextHolder.getLocale());
    }

    /**
     * 按消息Code获取消息（使用当前请求的Locale，带参数）
     * @param code 消息Code（不可为null/空）
     * @param args 消息参数（可为null）
     * @return 国际化消息（若不存在则返回Code）
     */
    public static String getMessageByCode(@NonNull String code, @Nullable Object... args) {
        return getMessageByCode(code, args, null, LocaleContextHolder.getLocale());
    }

    /**
     * 按消息Code获取消息（指定Locale，带参数，默认值兜底）
     * 【核心底层方法】：处理缓存、消息源获取、异常兜底
     * @param code 消息Code（不可为null/空）
     * @param args 消息参数（可为null）
     * @param defaultValue 消息不存在时的默认值（可为null，null时返回Code）
     * @param locale 指定的Locale（不可为null）
     * @return 国际化消息（优先缓存→消息源→默认值→Code）
     */
    public static String getMessageByCode(
            @NonNull String code,
            @Nullable Object[] args,
            @Nullable String defaultValue,
            @NonNull Locale locale
    ) {
        // 基础参数校验
        if (code.isBlank()) {
            LogUtil.warn("消息Code不可为空，返回空字符串");
            return "";
        }
        Objects.requireNonNull(locale, "Locale不可为null");

        // 构建缓存Key（Code+Locale，避免不同Locale消息冲突）
        String cacheKey = buildCacheKey(code, locale);

        // 尝试从缓存获取（缓存未过期则直接返回）
        CachedMessage cachedMessage = MESSAGE_CACHE.get(cacheKey);
        if (cachedMessage != null && !cachedMessage.isExpired()) {
            LogUtil.info("缓存命中：key={}, 消息={}", cacheKey, cachedMessage.getContent());
            return formatMessageWithArgs(cachedMessage.getContent(), args);
        }

        // 缓存未命中/已过期：从MessageSource获取消息
        String message = null;
        try {
            // 检查MessageSource是否初始化（避免启动时提前调用）
            if (messageSource == null) {
                LogUtil.error("MessageSource未初始化，无法获取消息（Code={}）", null, code);
            } else {
                // 调用MessageSource获取消息（带参数）
                message = messageSource.getMessage(code, args, null, locale);
            }
        } catch (Exception e) {
            // 捕获所有异常（如参数格式错误、消息源异常等），避免影响业务流程
            LogUtil.error("获取消息失败（Code={}, Locale={}）", null, code, locale, e);
        }

        // 消息兜底处理（消息不存在时用默认值/Code）
        String finalMessage = processFallbackMessage(code, message, defaultValue);

        // 存入缓存（后续调用可复用）
        MESSAGE_CACHE.put(cacheKey, new CachedMessage(finalMessage));
        LogUtil.info("缓存更新：key={}, 消息={}", cacheKey, finalMessage);

        // 返回最终消息（若有参数则格式化，无参数直接返回）
        return formatMessageWithArgs(finalMessage, args);
    }


    // ==================== 辅助方法：缓存、格式化、兜底 ====================
    /**
     * 构建缓存Key（Code + Locale的语言+国家，如 "common.success_zh_CN"）
     */
    private static String buildCacheKey(@NonNull String code, @NonNull Locale locale) {
        return code + "_" + locale.getLanguage() + "_" + locale.getCountry();
    }

    /**
     * 消息参数格式化（处理参数为null的情况，避免MessageFormat抛异常）
     * @param message 原始消息（如 "参数{0}不能为空"）
     * @param args 消息参数（可为null）
     * @return 格式化后的消息
     */
    private static String formatMessageWithArgs(@NonNull String message, @Nullable Object[] args) {
        if (args == null || args.length == 0) {
            return message; // 无参数直接返回
        }
        try {
            // 使用MessageFormat格式化参数（支持{0}、{1}等占位符）
            return new java.text.MessageFormat(message).format(args);
        } catch (Exception e) {
            LogUtil.warn("消息参数格式化失败（消息={}, 参数={}）", message, args, e);
            return message; // 格式化失败返回原始消息，避免业务异常
        }
    }

    /**
     * 消息兜底处理（消息不存在时的 fallback 逻辑）
     * @param code 消息Code
     * @param message 从MessageSource获取的消息（可能为null）
     * @param defaultValue 自定义默认值（可能为null）
     * @return 兜底后的消息
     */
    private static String processFallbackMessage(
            @NonNull String code,
            @Nullable String message,
            @Nullable String defaultValue
    ) {
        if (message != null && !message.isBlank()) {
            return message; // 消息存在，直接返回
        }
        // 消息不存在：用默认值→Code兜底
        if (defaultValue != null && !defaultValue.isBlank()) {
            LogUtil.warn("消息不存在（Code={}），使用默认值：{}", code, defaultValue);
            return defaultValue;
        } else {
            LogUtil.warn("消息不存在且无默认值（Code={}），返回Code作为兜底", code);
            return code;
        }
    }


    // ==================== 缓存管理方法（生产环境必备） ====================
    /**
     * 手动清理所有缓存（如消息配置更新后，通过接口调用触发）
     */
    public static void clearAllCache() {
        MESSAGE_CACHE.clear();
        LogUtil.info("消息缓存已全部清理");
    }

    /**
     * 重载方法：清理指定Code的所有Locale缓存（最常用场景）
     * @param code 消息Code（不可为null/空）
     */
    public static void clearCacheByCode(@NonNull String code) {
        // 调用原方法，传入null表示清理该Code的所有Locale缓存
        clearCacheByCode(code, null);
    }

    /**
     * 手动清理指定Code的缓存（精确清理，避免全量清理影响性能）
     * @param code 消息Code（不可为null/空）
     * @param locale 指定的Locale（可为null，null时清理所有Locale的该Code缓存）
     */
    public static void clearCacheByCode(@NonNull String code, @Nullable Locale locale) {
        if (code.isBlank()) {
            LogUtil.warn("清理缓存失败：消息Code不可为空");
            return;
        }
        if (locale == null) {
            // 清理所有Locale的该Code缓存（遍历删除匹配前缀的Key）
            MESSAGE_CACHE.keySet().removeIf(key -> key.startsWith(code + "_"));
            LogUtil.info("清理所有Locale的消息缓存（Code={}）", code);
        } else {
            // 清理指定Locale的该Code缓存
            String cacheKey = buildCacheKey(code, locale);
            MESSAGE_CACHE.remove(cacheKey);
            LogUtil.info("清理指定Locale的消息缓存（Code={}, Locale={}）", code, locale);
        }
    }

    /**
     * 按前缀清理缓存（用于清理整个模块的消息，如 "file." 前缀对应file模块）
     * @param prefix 消息Code前缀（如 "file."、"auth."，不可为null/空）
     */
    public static void clearCacheByPrefix(@NonNull String prefix) {
        if (prefix.isBlank()) {
            LogUtil.warn("清理缓存失败：前缀不可为空");
            return;
        }
        // 统计清理的缓存数量（便于日志跟踪）
        int initialSize = MESSAGE_CACHE.size();
        // 移除所有Key以prefix开头的缓存项（如 "file.upload.success_zh_CN"）
        MESSAGE_CACHE.keySet().removeIf(key -> key.startsWith(prefix));
        int clearedCount = initialSize - MESSAGE_CACHE.size();
        LogUtil.info("按前缀清理消息缓存（prefix={}），共清理 {} 条缓存", prefix, clearedCount);
    }

    // ==================== 内部静态类：缓存消息封装（含过期时间） ====================
    /**
     * 缓存消息封装类：存储消息内容和过期时间
     */
    private static class CachedMessage {
        // Getter
        /** 消息内容 */
        @Getter
        private final String content;
        /** 缓存过期时间戳（毫秒） */
        private final long expireTimestamp;

        /**
         * 构造方法：默认使用全局缓存过期时间
         */
        public CachedMessage(@NonNull String content) {
            this.content = content;
            this.expireTimestamp = System.currentTimeMillis() + CACHE_EXPIRE_DURATION.toMillis();
        }

        /**
         * 检查缓存是否过期
         * @return true=已过期，false=未过期
         */
        public boolean isExpired() {
            return System.currentTimeMillis() > expireTimestamp;
        }
    }
}